<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>流光效果</title>
    <!-- 这里可以添加CSS样式文件 -->
  </head>
  <body>
    <script type="importmap">
      {
        "imports": {
          "three": "./threejs/build/three.module.js",
          "three/addons/": "./threejs/examples/jsm/"
        }
      }
    </script>
    <script type="module">
      import * as THREE from "three";
      import { GUI } from "three/addons/libs/lil-gui.module.min.js";

      import { OrbitControls } from "three/addons/controls/OrbitControls.js";
      import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js";
      import { RenderPass } from "three/addons/postprocessing/RenderPass.js";
      import { UnrealBloomPass } from "three/addons/postprocessing/UnrealBloomPass.js";

      class PathPoint {
        constructor() {
          this.pos = new THREE.Vector3();
          this.dir = new THREE.Vector3();
          this.right = new THREE.Vector3();
          this.up = new THREE.Vector3(); // normal
          this.dist = 0; // distance from start
          this.widthScale = 1; // for corner
          this.sharp = false; // marks as sharp corner
        }

        lerpPathPoints(p1, p2, alpha) {
          this.pos.lerpVectors(p1.pos, p2.pos, alpha);
          this.dir.lerpVectors(p1.dir, p2.dir, alpha);
          this.up.lerpVectors(p1.up, p2.up, alpha);
          this.right.lerpVectors(p1.right, p2.right, alpha);
          this.dist = (p2.dist - p1.dist) * alpha + p1.dist;
          this.widthScale =
            (p2.widthScale - p1.widthScale) * alpha + p1.widthScale;
        }

        copy(source) {
          this.pos.copy(source.pos);
          this.dir.copy(source.dir);
          this.up.copy(source.up);
          this.right.copy(source.right);
          this.dist = source.dist;
          this.widthScale = source.widthScale;
        }
      }

      const helpVec3_1 = new THREE.Vector3();
      const helpVec3_2 = new THREE.Vector3();
      const helpVec3_3 = new THREE.Vector3();
      const helpMat4 = new THREE.Matrix4();
      const helpCurve = new THREE.QuadraticBezierCurve3();

      function _getCornerBezierCurve(
        last,
        current,
        next,
        cornerRadius,
        firstCorner,
        out
      ) {
        const lastDir = helpVec3_1.subVectors(current, last);
        const nextDir = helpVec3_2.subVectors(next, current);

        const lastDirLength = lastDir.length();
        const nextDirLength = nextDir.length();

        lastDir.normalize();
        nextDir.normalize();

        // cornerRadius can not bigger then lineDistance / 2, auto fix this
        const v0Dist = Math.min(
          (firstCorner ? lastDirLength / 2 : lastDirLength) * 0.999999,
          cornerRadius
        );
        out.v0.copy(current).sub(lastDir.multiplyScalar(v0Dist));

        out.v1.copy(current);

        const v2Dist = Math.min((nextDirLength / 2) * 0.999999, cornerRadius);
        out.v2.copy(current).add(nextDir.multiplyScalar(v2Dist));

        return out;
      }

      /**
       * PathPointList
       * input points to generate a PathPoint list
       */
      class PathPointList {
        constructor() {
          this.array = []; // path point array
          this.count = 0;
        }

        /**
         * Set points
         * @param {THREE.Vector3[]} points key points array
         * @param {number} cornerRadius? the corner radius. set 0 to disable round corner. default is 0.1
         * @param {number} cornerSplit? the corner split. default is 10.
         * @param {number} up? force up. default is auto up (calculate by tangent).
         * @param {boolean} close? close path. default is false.
         */
        set(
          points,
          cornerRadius = 0.1,
          cornerSplit = 10,
          up = null,
          close = false
        ) {
          points = points.slice(0);

          if (points.length < 2) {
            console.warn("PathPointList: points length less than 2.");
            this.count = 0;
            return;
          }

          // Auto close
          if (close && !points[0].equals(points[points.length - 1])) {
            points.push(new THREE.Vector3().copy(points[0]));
          }

          // Generate path point list
          for (let i = 0, l = points.length; i < l; i++) {
            if (i === 0) {
              this._start(points[i], points[i + 1], up);
            } else if (i === l - 1) {
              if (close) {
                // Connect end point and start point
                this._corner(
                  points[i],
                  points[1],
                  cornerRadius,
                  cornerSplit,
                  up
                );

                // Fix start point
                const dist = this.array[0].dist; // should not copy dist
                this.array[0].copy(this.array[this.count - 1]);
                this.array[0].dist = dist;
              } else {
                this._end(points[i]);
              }
            } else {
              this._corner(
                points[i],
                points[i + 1],
                cornerRadius,
                cornerSplit,
                up
              );
            }
          }
        }

        /**
         * Get distance of this path
         * @return {number}
         */
        distance() {
          if (this.count > 0) {
            return this.array[this.count - 1].dist;
          }
          return 0;
        }

        _getByIndex(index) {
          if (!this.array[index]) {
            this.array[index] = new PathPoint();
          }
          return this.array[index];
        }

        _start(current, next, up) {
          this.count = 0;

          const point = this._getByIndex(this.count);

          point.pos.copy(current);
          point.dir.subVectors(next, current);

          // init start up dir
          if (up) {
            point.up.copy(up);
          } else {
            // select an initial normal vector perpendicular to the first tangent vector
            let min = Number.MAX_VALUE;
            const tx = Math.abs(point.dir.x);
            const ty = Math.abs(point.dir.y);
            const tz = Math.abs(point.dir.z);
            if (tx < min) {
              min = tx;
              point.up.set(1, 0, 0);
            }
            if (ty < min) {
              min = ty;
              point.up.set(0, 1, 0);
            }
            if (tz < min) {
              point.up.set(0, 0, 1);
            }
          }

          point.right.crossVectors(point.dir, point.up).normalize();
          point.up.crossVectors(point.right, point.dir).normalize();
          point.dist = 0;
          point.widthScale = 1;
          point.sharp = false;

          point.dir.normalize();

          this.count++;
        }

        _end(current) {
          const lastPoint = this.array[this.count - 1];
          const point = this._getByIndex(this.count);

          point.pos.copy(current);
          point.dir.subVectors(current, lastPoint.pos);
          const dist = point.dir.length();
          point.dir.normalize();

          point.up.copy(lastPoint.up); // copy last up

          const vec = helpVec3_1.crossVectors(lastPoint.dir, point.dir);
          if (vec.length() > Number.EPSILON) {
            vec.normalize();
            const theta = Math.acos(
              Math.min(Math.max(lastPoint.dir.dot(point.dir), -1), 1)
            ); // clamp for floating pt errors
            point.up.applyMatrix4(helpMat4.makeRotationAxis(vec, theta));
          }

          point.right.crossVectors(point.dir, point.up).normalize();

          point.dist = lastPoint.dist + dist;
          point.widthScale = 1;
          point.sharp = false;

          this.count++;
        }

        _corner(current, next, cornerRadius, cornerSplit, up) {
          if (cornerRadius > 0 && cornerSplit > 0) {
            const lastPoint = this.array[this.count - 1];
            const curve = _getCornerBezierCurve(
              lastPoint.pos,
              current,
              next,
              cornerRadius,
              this.count - 1 === 0,
              helpCurve
            );
            const samplerPoints = curve.getPoints(cornerSplit); // TODO optimize

            for (let f = 0; f < cornerSplit; f++) {
              this._sharpCorner(
                samplerPoints[f],
                samplerPoints[f + 1],
                up,
                f === 0 ? 1 : 0
              );
            }

            if (!samplerPoints[cornerSplit].equals(next)) {
              this._sharpCorner(samplerPoints[cornerSplit], next, up, 2);
            }
          } else {
            this._sharpCorner(current, next, up, 0, true);
          }
        }

        // dirType: 0 - use middle dir / 1 - use last dir / 2- use next dir
        _sharpCorner(current, next, up, dirType = 0, sharp = false) {
          const lastPoint = this.array[this.count - 1];
          const point = this._getByIndex(this.count);

          const lastDir = helpVec3_1.subVectors(current, lastPoint.pos);
          const nextDir = helpVec3_2.subVectors(next, current);

          const lastDirLength = lastDir.length();

          lastDir.normalize();
          nextDir.normalize();

          point.pos.copy(current);

          if (dirType === 1) {
            point.dir.copy(lastDir);
          } else if (dirType === 2) {
            point.dir.copy(nextDir);
          } else {
            point.dir.addVectors(lastDir, nextDir);
            point.dir.normalize();
          }

          if (up) {
            if (point.dir.dot(up) === 1) {
              point.right.crossVectors(nextDir, up).normalize();
            } else {
              point.right.crossVectors(point.dir, up).normalize();
            }

            point.up.crossVectors(point.right, point.dir).normalize();
          } else {
            point.up.copy(lastPoint.up);

            const vec = helpVec3_3.crossVectors(lastPoint.dir, point.dir);
            if (vec.length() > Number.EPSILON) {
              vec.normalize();
              const theta = Math.acos(
                Math.min(Math.max(lastPoint.dir.dot(point.dir), -1), 1)
              ); // clamp for floating pt errors
              point.up.applyMatrix4(helpMat4.makeRotationAxis(vec, theta));
            }

            point.right.crossVectors(point.dir, point.up).normalize();
          }

          point.dist = lastPoint.dist + lastDirLength;

          const _cos = lastDir.dot(nextDir);
          point.widthScale =
            Math.min(1 / Math.sqrt((1 + _cos) / 2), 1.415) || 1;
          point.sharp = Math.abs(_cos - 1) > 0.05 && sharp;

          this.count++;
        }
      }

      /**
       * PathGeometry
       */
      class PathGeometry extends THREE.BufferGeometry {
        /**
         * @param {Object|Number} initData - If initData is number, geometry init by empty data and set it as the max vertex. If initData is Object, it contains pathPointList and options.
         * @param {Boolean} [generateUv2=false]
         */
        constructor(initData = 3000, generateUv2 = false) {
          super();

          if (isNaN(initData)) {
            this._initByData(
              initData.pathPointList,
              initData.options,
              initData.usage,
              generateUv2
            );
          } else {
            this._initByMaxVertex(initData, generateUv2);
          }
        }

        _initByMaxVertex(maxVertex, generateUv2) {
          this.setAttribute(
            "position",
            new THREE.BufferAttribute(
              new Float32Array(maxVertex * 3),
              3
            ).setUsage(THREE.DynamicDrawUsage)
          );
          this.setAttribute(
            "normal",
            new THREE.BufferAttribute(
              new Float32Array(maxVertex * 3),
              3
            ).setUsage(THREE.DynamicDrawUsage)
          );
          this.setAttribute(
            "uv",
            new THREE.BufferAttribute(
              new Float32Array(maxVertex * 2),
              2
            ).setUsage(THREE.DynamicDrawUsage)
          );
          if (generateUv2) {
            this.setAttribute(
              "uv2",
              new THREE.BufferAttribute(
                new Float32Array(maxVertex * 2),
                2
              ).setUsage(THREE.DynamicDrawUsage)
            );
          }

          this.drawRange.start = 0;
          this.drawRange.count = 0;

          this.setIndex(
            maxVertex > 65536
              ? new THREE.Uint32BufferAttribute(maxVertex * 3, 1)
              : new THREE.Uint16BufferAttribute(maxVertex * 3, 1)
          );
        }

        _initByData(pathPointList, options = {}, usage, generateUv2) {
          const vertexData = generatePathVertexData(
            pathPointList,
            options,
            generateUv2
          );

          if (vertexData && vertexData.count !== 0) {
            this.setAttribute(
              "position",
              new THREE.BufferAttribute(
                new Float32Array(vertexData.position),
                3
              ).setUsage(usage || THREE.StaticDrawUsage)
            );
            this.setAttribute(
              "normal",
              new THREE.BufferAttribute(
                new Float32Array(vertexData.normal),
                3
              ).setUsage(usage || THREE.StaticDrawUsage)
            );
            this.setAttribute(
              "uv",
              new THREE.BufferAttribute(
                new Float32Array(vertexData.uv),
                2
              ).setUsage(usage || THREE.StaticDrawUsage)
            );
            if (generateUv2) {
              this.setAttribute(
                "uv2",
                new THREE.BufferAttribute(
                  new Float32Array(vertexData.uv2),
                  2
                ).setUsage(usage || THREE.StaticDrawUsage)
              );
            }

            this.setIndex(
              vertexData.position.length / 3 > 65536
                ? new THREE.Uint32BufferAttribute(vertexData.indices, 1)
                : new THREE.Uint16BufferAttribute(vertexData.indices, 1)
            );
          } else {
            this._initByMaxVertex(2, generateUv2);
          }
        }

        /**
         * Update geometry by PathPointList instance
         * @param {PathPointList} pathPointList
         * @param {Object} options
         * @param {Number} [options.width=0.1]
         * @param {Number} [options.progress=1]
         * @param {Boolean} [options.arrow=true]
         * @param {String} [options.side='both'] - "left"/"right"/"both"
         */
        update(pathPointList, options = {}) {
          const generateUv2 = !!this.getAttribute("uv2");

          const vertexData = generatePathVertexData(
            pathPointList,
            options,
            generateUv2
          );

          if (vertexData) {
            this._updateAttributes(
              vertexData.position,
              vertexData.normal,
              vertexData.uv,
              generateUv2 ? vertexData.uv2 : null,
              vertexData.indices
            );
            this.drawRange.count = vertexData.count;
          } else {
            this.drawRange.count = 0;
          }
        }

        _resizeAttribute(name, len) {
          let attribute = this.getAttribute(name);
          while (attribute.array.length < len) {
            const oldLength = attribute.array.length;
            const newAttribute = new THREE.BufferAttribute(
              new Float32Array(oldLength * 2),
              attribute.itemSize,
              attribute.normalized
            );
            newAttribute.name = attribute.name;
            newAttribute.usage = attribute.usage;
            this.setAttribute(name, newAttribute);
            attribute = newAttribute;
          }
        }

        _resizeIndex(len) {
          let index = this.getIndex();
          while (index.array.length < len) {
            const oldLength = index.array.length;
            const newIndex = new THREE.BufferAttribute(
              oldLength * 2 > 65535
                ? new Uint32Array(oldLength * 2)
                : new Uint16Array(oldLength * 2),
              1
            );
            newIndex.name = index.name;
            newIndex.usage = index.usage;
            this.setIndex(newIndex);
            index = newIndex;
          }
        }

        _updateAttributes(position, normal, uv, uv2, indices) {
          this._resizeAttribute("position", position.length);
          const positionAttribute = this.getAttribute("position");
          positionAttribute.array.set(position, 0);
          positionAttribute.updateRange.count = position.length;
          positionAttribute.needsUpdate = true;

          this._resizeAttribute("normal", normal.length);
          const normalAttribute = this.getAttribute("normal");
          normalAttribute.array.set(normal, 0);
          normalAttribute.updateRange.count = normal.length;
          normalAttribute.needsUpdate = true;

          this._resizeAttribute("uv", uv.length);
          const uvAttribute = this.getAttribute("uv");
          uvAttribute.array.set(uv, 0);
          uvAttribute.updateRange.count = uv.length;
          uvAttribute.needsUpdate = true;

          if (uv2) {
            this._resizeAttribute("uv2", uv2.length);
            const uv2Attribute = this.getAttribute("uv2");
            uv2Attribute.array.set(uv2, 0);
            uv2Attribute.updateRange.count = uv2.length;
            uv2Attribute.needsUpdate = true;
          }

          this._resizeIndex(indices.length);
          const indexAttribute = this.getIndex();
          indexAttribute.set(indices, 0);
          indexAttribute.updateRange.count = indices.length;
          indexAttribute.needsUpdate = true;
        }
      }

      // Vertex Data Generate Functions

      function generatePathVertexData(
        pathPointList,
        options,
        generateUv2 = false
      ) {
        const width = options.width || 0.1;
        const progress = options.progress !== undefined ? options.progress : 1;
        const arrow = options.arrow !== undefined ? options.arrow : true;
        const side = options.side !== undefined ? options.side : "both";

        const halfWidth = width / 2;
        const sideWidth = side !== "both" ? width / 2 : width;
        const totalDistance = pathPointList.distance();
        const progressDistance = progress * totalDistance;
        if (totalDistance == 0) {
          return null;
        }

        const sharpUvOffset = halfWidth / sideWidth;
        const sharpUvOffset2 = halfWidth / totalDistance;

        let count = 0;

        // modify data
        const position = [];
        const normal = [];
        const uv = [];
        const uv2 = [];
        const indices = [];
        let verticesCount = 0;

        const right = new THREE.Vector3();
        const left = new THREE.Vector3();

        // for sharp corners
        const leftOffset = new THREE.Vector3();
        const rightOffset = new THREE.Vector3();
        const tempPoint1 = new THREE.Vector3();
        const tempPoint2 = new THREE.Vector3();

        function addVertices(pathPoint) {
          const first = position.length === 0;
          const sharpCorner = pathPoint.sharp && !first;

          // const uvDist = pathPoint.dist / sideWidth; // origin
          // 此处修改了uv绘制逻辑，uv从起点到终点为0-1
          const uvDist = pathPoint.dist / totalDistance;
          const uvDist2 = pathPoint.dist / totalDistance;

          const dir = pathPoint.dir;
          const up = pathPoint.up;
          const _right = pathPoint.right;

          if (side !== "left") {
            right.copy(_right).multiplyScalar(halfWidth * pathPoint.widthScale);
          } else {
            right.set(0, 0, 0);
          }

          if (side !== "right") {
            left.copy(_right).multiplyScalar(-halfWidth * pathPoint.widthScale);
          } else {
            left.set(0, 0, 0);
          }

          right.add(pathPoint.pos);
          left.add(pathPoint.pos);

          if (sharpCorner) {
            leftOffset.fromArray(position, position.length - 6).sub(left);
            rightOffset.fromArray(position, position.length - 3).sub(right);

            const leftDist = leftOffset.length();
            const rightDist = rightOffset.length();

            const sideOffset = leftDist - rightDist;
            let longerOffset, longEdge;

            if (sideOffset > 0) {
              longerOffset = leftOffset;
              longEdge = left;
            } else {
              longerOffset = rightOffset;
              longEdge = right;
            }

            tempPoint1
              .copy(longerOffset)
              .setLength(Math.abs(sideOffset))
              .add(longEdge);

            let _cos = tempPoint2
              .copy(longEdge)
              .sub(tempPoint1)
              .normalize()
              .dot(dir);
            let _len = tempPoint2.copy(longEdge).sub(tempPoint1).length();
            let _dist = _cos * _len * 2;

            tempPoint2.copy(dir).setLength(_dist).add(tempPoint1);

            if (sideOffset > 0) {
              position.push(
                tempPoint1.x,
                tempPoint1.y,
                tempPoint1.z, // 6
                right.x,
                right.y,
                right.z, // 5
                left.x,
                left.y,
                left.z, // 4
                right.x,
                right.y,
                right.z, // 3
                tempPoint2.x,
                tempPoint2.y,
                tempPoint2.z, // 2
                right.x,
                right.y,
                right.z // 1
              );

              verticesCount += 6;

              indices.push(
                verticesCount - 6,
                verticesCount - 8,
                verticesCount - 7,
                verticesCount - 6,
                verticesCount - 7,
                verticesCount - 5,

                verticesCount - 4,
                verticesCount - 6,
                verticesCount - 5,
                verticesCount - 2,
                verticesCount - 4,
                verticesCount - 1
              );

              count += 12;
            } else {
              position.push(
                left.x,
                left.y,
                left.z, // 6
                tempPoint1.x,
                tempPoint1.y,
                tempPoint1.z, // 5
                left.x,
                left.y,
                left.z, // 4
                right.x,
                right.y,
                right.z, // 3
                left.x,
                left.y,
                left.z, // 2
                tempPoint2.x,
                tempPoint2.y,
                tempPoint2.z // 1
              );

              verticesCount += 6;

              indices.push(
                verticesCount - 6,
                verticesCount - 8,
                verticesCount - 7,
                verticesCount - 6,
                verticesCount - 7,
                verticesCount - 5,

                verticesCount - 6,
                verticesCount - 5,
                verticesCount - 3,
                verticesCount - 2,
                verticesCount - 3,
                verticesCount - 1
              );

              count += 12;
            }

            normal.push(
              up.x,
              up.y,
              up.z,
              up.x,
              up.y,
              up.z,
              up.x,
              up.y,
              up.z,
              up.x,
              up.y,
              up.z,
              up.x,
              up.y,
              up.z,
              up.x,
              up.y,
              up.z
            );

            uv.push(
              uvDist - sharpUvOffset,
              0,
              uvDist - sharpUvOffset,
              1,
              uvDist,
              0,
              uvDist,
              1,
              uvDist + sharpUvOffset,
              0,
              uvDist + sharpUvOffset,
              1
            );

            if (generateUv2) {
              uv2.push(
                uvDist2 - sharpUvOffset2,
                0,
                uvDist2 - sharpUvOffset2,
                1,
                uvDist2,
                0,
                uvDist2,
                1,
                uvDist2 + sharpUvOffset2,
                0,
                uvDist2 + sharpUvOffset2,
                1
              );
            }
          } else {
            position.push(left.x, left.y, left.z, right.x, right.y, right.z);

            normal.push(up.x, up.y, up.z, up.x, up.y, up.z);

            uv.push(uvDist, 0, uvDist, 1);

            if (generateUv2) {
              uv2.push(uvDist2, 0, uvDist2, 1);
            }

            verticesCount += 2;

            if (!first) {
              indices.push(
                verticesCount - 2,
                verticesCount - 4,
                verticesCount - 3,
                verticesCount - 2,
                verticesCount - 3,
                verticesCount - 1
              );

              count += 6;
            }
          }
        }

        const sharp = new THREE.Vector3();
        function addStart(pathPoint) {
          const dir = pathPoint.dir;
          const up = pathPoint.up;
          const _right = pathPoint.right;

          const uvDist = pathPoint.dist / sideWidth;
          const uvDist2 = pathPoint.dist / totalDistance;

          if (side !== "left") {
            right.copy(_right).multiplyScalar(halfWidth * 2);
          } else {
            right.set(0, 0, 0);
          }

          if (side !== "right") {
            left.copy(_right).multiplyScalar(-halfWidth * 2);
          } else {
            left.set(0, 0, 0);
          }

          sharp.copy(dir).setLength(halfWidth * 3);

          right.add(pathPoint.pos);
          left.add(pathPoint.pos);
          sharp.add(pathPoint.pos);

          position.push(
            left.x,
            left.y,
            left.z,
            right.x,
            right.y,
            right.z,
            sharp.x,
            sharp.y,
            sharp.z
          );

          normal.push(up.x, up.y, up.z, up.x, up.y, up.z, up.x, up.y, up.z);

          uv.push(
            uvDist,
            side !== "both" ? (side !== "right" ? -2 : 0) : -0.5,
            uvDist,
            side !== "both" ? (side !== "left" ? 2 : 0) : 1.5,
            uvDist + 1.5,
            side !== "both" ? 0 : 0.5
          );

          if (generateUv2) {
            uv2.push(
              uvDist2,
              side !== "both" ? (side !== "right" ? -2 : 0) : -0.5,
              uvDist2,
              side !== "both" ? (side !== "left" ? 2 : 0) : 1.5,
              uvDist2 + (1.5 * width) / totalDistance,
              side !== "both" ? 0 : 0.5
            );
          }

          verticesCount += 3;

          indices.push(verticesCount - 1, verticesCount - 3, verticesCount - 2);

          count += 3;
        }

        let lastPoint;

        if (progressDistance > 0) {
          for (let i = 0; i < pathPointList.count; i++) {
            const pathPoint = pathPointList.array[i];

            if (pathPoint.dist > progressDistance) {
              const prevPoint = pathPointList.array[i - 1];
              lastPoint = new PathPoint();

              // linear lerp for progress
              const alpha =
                (progressDistance - prevPoint.dist) /
                (pathPoint.dist - prevPoint.dist);
              lastPoint.lerpPathPoints(prevPoint, pathPoint, alpha);

              addVertices(lastPoint);
              break;
            } else {
              addVertices(pathPoint);
            }
          }
        } else {
          lastPoint = pathPointList.array[0];
        }

        // build arrow geometry
        if (arrow) {
          lastPoint = lastPoint || pathPointList.array[pathPointList.count - 1];
          addStart(lastPoint);
        }

        return {
          position,
          normal,
          uv,
          uv2,
          indices,
          count,
        };
      }

      const { innerWidth, innerHeight } = window;
      const aspect = innerWidth / innerHeight;

      const param = {
        exposure: 2,
        bloomStrength: 2,
        bloomThreshold: 0.7,
        bloomRadius: 0,
      };
      class Base {
        constructor() {
          this.init();
          this.main();
        }
        main() {
          this.renderScene = new RenderPass(this.scene, this.camera);

          const bloomPass = new UnrealBloomPass(
            new THREE.Vector2(innerWidth, innerHeight),
            3,
            0,
            0
          );

          this.composer = new EffectComposer(this.renderer);
          this.composer.addPass(this.renderScene);
          this.composer.addPass(bloomPass);

          this.line = this.createPath([
            { x: -50, y: 1, z: -50 },
            { x: 50, y: 1, z: -50 },
            { x: 50, y: 1, z: 50 },
            { x: -50, y: 1, z: 50 },
            { x: -50, y: 1, z: -50 },
          ]);
          this.scene.add(this.line);

          this.gui = new GUI();

          this.gui.add(param, "exposure", 0.1, 2.0).onChange((value) => {
            this.renderer.toneMappingExposure = Math.pow(value, 4);
          });

          this.gui.add(param, "bloomThreshold", 0.0, 1.0).onChange((value) => {
            bloomPass.threshold = Number(value);
          });

          this.gui.add(param, "bloomStrength", 0.0, 3.0).onChange((value) => {
            bloomPass.strength = Number(value);
          });

          this.gui.add(param, "bloomRadius", 0.0, 1.0).onChange((value) => {
            bloomPass.radius = Number(value);
          });
        }
        init() {
          this.clock = new THREE.Clock();

          this.renderer = new THREE.WebGLRenderer({
            antialias: true,
            logarithmicDepthBuffer: true,
          });
          this.renderer.setPixelRatio(window.devicePixelRatio);
          this.renderer.setSize(innerWidth, innerHeight);
          this.renderer.setAnimationLoop(this.animate.bind(this));
          document.body.appendChild(this.renderer.domElement);

          this.camera = new THREE.PerspectiveCamera(60, aspect, 0.01, 10000);
          this.camera.position.set(50, 50, 50);

          this.scene = new THREE.Scene();

          this.controls = new OrbitControls(
            this.camera,
            this.renderer.domElement
          );

          const grid = new THREE.GridHelper(100);
          this.scene.add(grid);

          const light = new THREE.AmbientLight(0xffffff, 0.5);
          this.scene.add(light);
        }
        createPath(points) {
          const up = new THREE.Vector3(0, 1, 0);
          const pathPointList = new PathPointList();
          pathPointList.set(points, 0.5, 10, up, false);
          const geometry = new PathGeometry();
          geometry.update(pathPointList, {
            width: 0.3,
            arrow: false,
            side: "both",
          });
          const vertexShader = `
                        uniform float uTime;
                        varying vec2 vUv; 
                        varying vec4 uColor;



                        void main(){
                            vUv = uv;
                            gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
                        }
                    `;
          const fragmentShader = `
                        varying vec4 uColor;
                        uniform float uTime;
                        varying vec2 vUv; 

                        vec4 getColor(float l1,float l2,float l3,vec2 uv){

                            float s1 = abs(l1 - uv.x) ;
                            if(s1<0.05){
                                return vec4(0.5,.5 - 5.0 * s1 ,.7,1.0);
                            }
                            if(s1>0.95){
                                return vec4(0.5,.5 - 5.0 * (1.0-s1) ,.7,1.0);
                            }

                            float s2 = abs(l2 - uv.x) ;
                            if(s2 < 0.05){
                                return vec4(0.5,.5 - 5.0 * s2,.7,1.0);
                            }
                            if(s2>0.95){
                                return vec4(0.5,.5 - 5.0 * (1.0-s2),.7,1.0);
                            }

                            float s3 =abs(l3 - uv.x) ;
                            if(s3 < 0.05){
                                return vec4(0.5,.5 - 5.0 *s3,0.7,1.0);
                            }
                            if(s2>0.95){
                                return vec4(0.5,.5 - 5.0 *(1.0-s3),0.7,1.0);
                            }

                            // return vec4(1.0,0.5,.0,1.0);
                        }

                        void main(){
							float l1 = fract(uTime);
							float l2 = fract(l1+0.33);
							float l3 = fract(l2+0.33);

                            gl_FragColor = getColor(l1,l2,l3,vUv);
                            // gl_FragColor = uColor;
                        }
                    `;

          this.material = new THREE.ShaderMaterial({
            uniforms: {
              // map: { value: map },
              uTime: { value: 0 },
            },
            vertexShader,
            fragmentShader,
            transparent: true,
            side: THREE.DoubleSide,
          });
          const mesh = new THREE.Mesh(geometry, this.material);
          // mesh.material.onBeforeCompile = (shader) => {
          // 	shader.uniforms.uelapseTime = this.elapsedTime;
          // 	shader.uniforms.modelPosition = this.modelPosition;
          // 	shader.uniforms.seg = this.segment;
          // 	shader.uniforms.rColor = { value: new THREE.Color("#E5C015") }; // 路中间颜色
          // 	shader.uniforms.uColor = { value: new THREE.Color("#19B89E") }; // 边缘颜色
          // 	this.shaderModify(shader);
          // };
          return mesh;
        }
        animate() {
          const delta = this.clock.getDelta();

          this.controls.update();
          this.composer.render();
          // this.renderer.render(this.scene, this.camera);
          this.material.uniforms.uTime.value += delta / 6;
        }
      }
      new Base();
    </script>
  </body>
</html>
